﻿using System;
using System.ComponentModel;

namespace SharpGL.SceneGraph
{
    /// <summary>
    /// The Scene Container is the top-level object in a scene graph.
    /// </summary>
    public class SceneContainer1 : SceneElement
    {
        #region Member variables

        /// <summary>
        /// The parent scene.
        /// </summary>
        private Scene parentScene = null;

        #endregion

        private static void CreateInContext(OpenGL gl, SceneElement1 sceneElement)
        {
            if (sceneElement is IHasOpenGLContext)
                ((IHasOpenGLContext)sceneElement).CreateInContext(gl);

            foreach (Effect effect in sceneElement.Effects)
            {
                if (effect is IHasOpenGLContext)
                    ((IHasOpenGLContext)effect).CreateInContext(gl);
            }

            foreach (SceneElement1 child in sceneElement.Children)
                CreateInContext(gl, child);
        }

        private static void DestroyInContext(OpenGL gl, SceneElement1 sceneElement)
        {
            foreach (SceneElement1 child in sceneElement.Children)
                DestroyInContext(gl, child);
            
            foreach (Effect effect in sceneElement.Effects)
            {
                if (effect is IHasOpenGLContext)
                    ((IHasOpenGLContext)effect).DestroyInContext();
            }

            if (sceneElement is IHasOpenGLContext)
                ((IHasOpenGLContext)sceneElement).DestroyInContext();
        }

        /// <summary>
        /// Called when scene tree changed.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">A EffectChangeEventArgs that contains the event data.</param>
        private void OnSceneTreeChanged(object sender, SceneTreeChangeEventArgs e)
        {
            switch (e.Action)
            {
                case SceneTreeChangeAction.Add:
                    if (this is IHasOpenGLContext &&
                        e.SceneElement is IHasOpenGLContext &&
                        OpenGL.IsValidContext(((IHasOpenGLContext)this).CurrentOpenGLContext) &&
                        !OpenGL.IsValidContext(((IHasOpenGLContext)e.SceneElement).CurrentOpenGLContext))
                    {
                        CreateInContext(((IHasOpenGLContext)this).CurrentOpenGLContext, e.SceneElement);
                    }

                    break;
                
                case SceneTreeChangeAction.Remove:
                    if (e.SceneElement is IHasOpenGLContext &&
                        OpenGL.IsValidContext(((IHasOpenGLContext)e.SceneElement).CurrentOpenGLContext))
                    {
                        DestroyInContext(((IHasOpenGLContext)e.SceneElement).CurrentOpenGLContext, e.SceneElement);
                    }

                    break;
                
                case SceneTreeChangeAction.EffectAdded:
                    if (this is IHasOpenGLContext &&
                        e.Effect is IHasOpenGLContext &&
                        OpenGL.IsValidContext(((IHasOpenGLContext)this).CurrentOpenGLContext) &&
                        !OpenGL.IsValidContext(((IHasOpenGLContext)e.Effect).CurrentOpenGLContext))
                    {
                        ((IHasOpenGLContext)e.Effect).CreateInContext(((IHasOpenGLContext)this).CurrentOpenGLContext);
                    }

                    break;

                case SceneTreeChangeAction.EffectRemove:
                    if (e.Effect is IHasOpenGLContext &&
                        OpenGL.IsValidContext(((IHasOpenGLContext)e.Effect).CurrentOpenGLContext))
                    {
                        ((IHasOpenGLContext)e.Effect).DestroyInContext();
                    }

                    break;
            }

            DoSceneTreeChanged(e);
        }

        
        
        
        #region Protected Methods

        /// <summary>
        /// Called when element added to or removed from scenes
        /// </summary>
        /// <param name="sender">Scene element which children list changed.</param>
        protected override void OnSceneTreeChanged(Object sender)
        {
            var handler = SceneTreeChanged;
            if (handler != null)
                handler(sender, null);
        }

        #endregion

        #region Construction/Destruction

        /// <summary>
        /// Initializes a new instance of the <see cref="SceneContainer"/> class.
        /// </summary>
        public SceneContainer()
        {
            Name = "Scene Container";
        }

        #endregion

        #region Events

        /// <summary>
        /// Called when the new element added to or removed from scene tree.
        /// </summary>
        public event EventHandler SceneTreeChanged;

        #endregion

        #region Properties

        /// <summary>
        /// Gets the parent.
        /// </summary>
        [Description("The parent scene element."), Category("Scene Container")]
        public override SceneElement Parent
        {
            get
            {
                return null;
            }
            protected set
            {
                if (value != null)
                    throw new Exception("Scene container '" + Name + "' can't have a parent.");
            }
        }

        /// <summary>
        /// Gets or sets the parent scene.
        /// </summary>
        /// <value>
        /// The parent scene.
        /// </value>
        [Description("The parent scene."), Category("Scene Container")]
        public Scene ParentScene
        {
            get
            {
                return parentScene;
            }
            set
            {
                if (parentScene != value)
                {
                    DestroyInContext();

                    parentScene = value;

                    if (parentScene != null)
                        CreateInContext(parentScene.CurrentOpenGLContext);
                }
            }
        }

        #endregion
    }
}